//
//  EasyTcpSocket.m
//  wf-soket-new
//
//  Created by Rankin on 16/5/29.
//  Copyright © 2016年 Rankin. All rights reserved.
//

#import "EasyTcpSocket.h"
#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>
#import <sys/ioctl.h>
#import <net/if.h>
#import <netdb.h>
@implementation EasyTcpSocket

- (id)init{
    self = [super init];
    if (self) {
        self.sockDescriptor = -1;
    }
    
    return self;
}


//返回-1表示失败，正常返回socket的编号
- (int)connectHost:(NSString*)host port:(int)port timeout:(int)timeout{
    _sockDescriptor = socket(AF_INET,SOCK_STREAM,0);
    if (-1 == _sockDescriptor) {
        NSLog(@"初始化socket失败");
        return _sockDescriptor;
    }
    struct sockaddr_in addr;//目标地址
    addr.sin_family = AF_INET;//ipv4
    addr.sin_addr =*(struct in_addr *)gethostbyname([host UTF8String])->h_addr_list[0];
    addr.sin_port = htons(port);
    
    
    unsigned long ioctlParam = 1;
    ioctl(_sockDescriptor, FIONBIO,&ioctlParam);//设置为非阻塞模式
    
    //设置超时时间
    struct timeval timeoutval;
    timeoutval.tv_sec = timeout;
    timeoutval.tv_usec = 0;
    
    setsockopt(_sockDescriptor, SOL_SOCKET, SO_SNDTIMEO, &timeoutval, sizeof(timeoutval));//设置发送超时时间
    //    setsockopt(_sockDescriptor, SOL_SOCKET, SO_RCVTIMEO, &timeoutval, sizeof(timeoutval));//设置接受超时时间 现在需要不断的等待设备返回数据所以不设置
    
    fd_set set;
    FD_ZERO(&set);
    FD_SET(_sockDescriptor, &set);//定义fd_set为select设置超时时间准备
    
    int a = connect(_sockDescriptor, (struct sockaddr* )&addr, sizeof(addr));//非阻塞时connect很随意，不用判断返回值了
    //        if(a==-1)return; //返回值为0表示成功，非阻塞模式下肯定返回－1所以忽略
    
    int selectSigal = select(_sockDescriptor+1, NULL, &set, NULL, &timeoutval);//select 是阻塞的，直到socket文件能写才返回，或者等到超时时间到了就返回
    
    switch (selectSigal) {
        case -1:{
            //失败
            return -1;
        }
            break;
        case 0:{
            //超时
            return -1;
        }
            break;
        default:
        {
            //select正常
            int error =0;
            int len = 0;
            getsockopt(_sockDescriptor, SOL_SOCKET, SO_ERROR, &error, (socklen_t *)&len);
            if (error==0) {
                //连接无错误
                
                ioctlParam = 0;
                ioctl(_sockDescriptor, FIONBIO,&ioctlParam);//设置为阻塞模式，不然recv的时候没法阻塞读取
                
                _connectedIp = host;
                _connectedPort = port;
                return _sockDescriptor;
            }else{
                return -1;
            }
        }
            break;
    }
    
    
    
    return -1;
}


//失败返回-1，成功返回发送的数据长度
- (ssize_t)sendData:(void*)datas length:(long)len resendTimes:(int)resendTimes{
    if (_sockDescriptor==-1)return -1;
    
    ssize_t sendDataLength =-1;
    if( resendTimes <=0){
        resendTimes = 1;
    }
    //
    NSLog(@"sending:%@",[NSData dataWithBytes:datas length:len]);
    //用strlen(datas)来发送的话会坑碰到00ff的就会丢数据，所以得传入数据长度，避免发不全的问题
    while ((sendDataLength = send(_sockDescriptor,datas,len,0))==-1&&resendTimes>0) {
        resendTimes--;
    }
    if (sendDataLength==-1) {
        //todo 重发机制
        NSLog(@"发送超时或失败");
        [self close];
        return -1;
    }
    
    NSLog(@"sended:%ld",sendDataLength);
    
    return sendDataLength;
}

//返回-1表示失败，0成功  这样容易导致delegate数据接收不全。。。没有好的想法了 todo
//- (int)recvDatasFromSock{
//    if (_sockDescriptor==-1) {
//        return -1;
//    }
//    dispatch_async(dispatch_get_global_queue(0, 0)  , ^{
//        while (true) {
//            void *recvDatas = malloc(9126);
//            int length = sizeof(recvDatas);
//            long recvCount = recv(_sockDescriptor,recvDatas,length,0);
//            if(recvCount==-1){
//                //接收数据为-1时表示已经断开了
//                [self close];
//                //todo 通知js断线
//                
//                NSLog(@"断线");
//                return ;
//            }
//            recvDatas = realloc(recvDatas, recvCount);
//            NSLog(@"recv:%ld%@",recvCount,[NSData dataWithBytes:recvDatas length:recvCount]);
//            if (recvCount>0) {
////                dispatch_async(dispatch_get_main_queue(),^{
//                if ([_delegate respondsToSelector:@selector(recvMessage:from:port:)]) {
//                    char* copy = malloc(recvCount);
//                    strcpy(copy, recvDatas);
//                    [_delegate recvMessage:copy from:NULL port:0];
//                }
////                });
//            }
//        }
//    });
//
//    return 0;
//}

- (bool)isConnected{
    return _sockDescriptor!=-1;
}

-(void)close{
    close(_sockDescriptor);
    _connectedIp = nil;
    _connectedPort =0;
    _sockDescriptor = -1;
}

@end
